#ifndef __D3DRENDERER_H__
#define __D3DRENDERER_H__

namespace Renderer
{
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	const static float s_RadToDeg = ( 180.0f / D3DX_PI );
	const static float s_DegToRad = ( D3DX_PI / 180.0f );

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	typedef std::size_t DataKey;
	template<typename T>
	inline DataKey& MakeKey(DataKey& _rKey, const T& _rData)
	{
		boost::hash_combine( _rKey, _rData );
		return _rKey;
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	typedef D3DXVECTOR3 Vector3;
	typedef D3DXVECTOR4 Vector4;
	typedef D3DXMATRIX	Matrix;

	class D3DEffect;
	typedef D3DEffect* D3DEffectPtr;

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	class Renderable
	{
	public:
		Renderable() {};
		virtual ~Renderable() {};

		// called once per frame
		virtual void Begin() {};
		virtual void End() {};

		// called once per render pass
		virtual void PreRender(D3DEffectPtr _pEffect, const D3DXPASS_DESC& _rPassDesc) {};
		virtual void Render() {};
		virtual const Matrix& GetWorldTransform() const = 0;

	};
	typedef vector<Renderable*> RenderableVec;

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	#define D3DFX_ADDFLOATPARAM( Name ) \
		private: D3DXHANDLE m_h##Name; \
		public: void Init##Name() { m_h##Name = m_pEffect->GetParameterBySemantic( NULL, #Name ); } \
		public: void Set##Name(const float& _rData) { if ( m_h##Name ) { m_pEffect->SetFloat( m_h##Name, _rData ) ; } } \
		public: D3DXHANDLE GetHandle##Name() { return m_h##Name ; } \

	#define D3DFX_ADDMATRIXPARAM( Name ) \
		private: D3DXHANDLE m_h##Name; \
		public: void Init##Name() { m_h##Name = m_pEffect->GetParameterBySemantic( NULL, #Name ); } \
		public: void Set##Name(const Matrix* _pData) { if ( m_h##Name ) { m_pEffect->SetMatrix( m_h##Name, _pData ) ; } } \
		public: D3DXHANDLE GetHandle##Name() { return m_h##Name ; } \

	class D3DRenderer;

	class D3DEffect
	{
	public:
		D3DEffect(D3DRenderer& _rRenderer);
		~D3DEffect();

		bool Load(const wstring& _wstrFilename);
		void Unload();

		LPD3DXEFFECT Get();

		D3DFX_ADDMATRIXPARAM( WORLDVIEWPROJ )
		D3DFX_ADDMATRIXPARAM( VIEW )
		D3DFX_ADDMATRIXPARAM( WORLD )
		D3DFX_ADDFLOATPARAM( ZNEAR )
		D3DFX_ADDFLOATPARAM( ZFAR )

	protected:
		void GetParameters();

	protected:
		D3DRenderer&	m_rRenderer;
		LPD3DXEFFECT	m_pEffect;
	};

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	class D3DRenderer
	{
	public:
		struct VideoInfo
		{
			bool			m_bFullscreen;
			unsigned int	m_uWidth;
			unsigned int	m_uHeight;
		};

	public:
		D3DRenderer(HWND _hWnd);
		~D3DRenderer();

		bool OpenVideo(const VideoInfo& _rInfo);
		void CloseVideo();

		void SetDefautSettings(const VideoInfo& _rInfo);

		bool LoadEffect(const string& _strName, const wstring& _wstrFilename);
		void UnloadEffect(const string& _strName);

		LPDIRECT3DDEVICE9 GetDevice();
		const float& GetPixelSize() const;

		void GetCameraDir(Vector3& _oFrontDir, Vector3& _oRightDir, Vector3& _oUpDir, const bool _bInv = false);
		Vector3& GetCameraRot();
		Vector3& GetCameraPos();
		const Matrix& GetViewMatrix(const bool _bInv = false) const;
		const Matrix& GetViewProjMatrix() const;

		void UpdateCamera(const bool& _bRotChanged, const bool& _bPosChanged);

		void RenderRequest(Renderable* _pRenderable, const string& _strEffectName, const float _fZNear = 1.0f, const float _fZFar = 1000000.0f);

		void Begin();
		void Process();
		void End();

	protected:
		typedef map<string, D3DEffectPtr> EffectMap;
		struct RenderInfo
		{
			Renderable*	m_pRenderable;
			float		m_fZNear;
			float		m_fZFar;
		};
		typedef vector<RenderInfo> RenderInfoVec;
		typedef map<D3DEffectPtr, RenderInfoVec> RenderableMap;

	protected:
		D3DEffectPtr GetEffect(const string& _strName);
		void RenderEffect(D3DEffectPtr _pEffect, RenderInfoVec& _vRenderList);

	protected:
		HWND				m_hWnd;
		LPDIRECT3D9			m_pD3D;
		LPDIRECT3DDEVICE9	m_pDevice;

		RenderableMap		m_mRenderList;
		EffectMap			m_mEffects;

		float				m_fFovy;
		float				m_fAspect;
		float				m_fZNear;
		float				m_fZFar;
		float				m_fPixelSize;

		// camera
		Vector3				m_oCamRot;
		Vector3				m_oCamPos;
		Matrix				m_oCameraRotation;
		Matrix				m_oCameraPosition;
		Matrix				m_oWorld;
		Matrix				m_oView;
		Matrix				m_oProj;
		Matrix				m_oInvView;
		Matrix				m_oViewProj;
		Matrix				m_oWorldViewProj;
	};
}

#endif // __D3DRENDERER_H__